/***
 * @Description: 本文件是关于ICMP协议的C语言文件，主要包括其数据结构的定义，ping功能的实现，不可达差错功能的实现
 * @Author: Liverpool
 * @Date: 2024-07-13 19:31:36
 * @LastEditTime: 2024-07-13 19:37:32
 * @LastEditors: Liverpool
 */
#include "icmpv4.h"
#include "debug.h"
#include "ipv4.h"
#include "protocol.h"
#include "raw.h"

#if Debug_Display_Enable(Debug_ICMPv4)
/***
 * @description: 展示ICMP数据包的相应信息
 * @Author: Liverpool
 * @Date: 2024-07-15 17:20:22
 * @return {*}
 * @param {D_ICMPv4_Packet} *packet 需要展示的数据包的内存地址
 */
static void display_icmpv4_packet(D_ICMPv4_Packet *packet)
{
    plat_printf("---------------------ICMP Packet---------------------\n");
    plat_printf("\ttype:%d\n", packet->header.type);
    plat_printf("\tcode:%d\n", packet->header.code);
    plat_printf("\tchecksum:%d\n", packet->header.checksum);
    plat_printf("---------------------ICMP end---------------------\n");
}
#else
#define display_icmpv4_packet(packet)
#endif

/***
 * @description: (静态函数)检查ICMP数据包是否合法
 * @Author: Liverpool
 * @Date: 2024-07-15 11:47:53
 * @return {*} 0:代表合法  <0:代表不合法
 * @param {D_ICMPv4_Packet} *icmp_packet icmp数据包的内存地址
 * @param {int} size 整个数据包的尺寸大小
 * @param {D_Packet} *buf 接收到的数据包的内存位置
 */
static net_err_t is_ICMPv4_packet_ok(D_ICMPv4_Packet *icmp_packet, int size, D_Packet *buf)
{
    if (size <= sizeof(D_ICMPv4_Header))
    {
        debug_Warning(Debug_ICMPv4, "IMCP packet size error...");
        return NET_ERR_SIZE;
    }
    uint16_t checksum = packet_checksum_16(buf, size, 0, 1);
    if (checksum != 0)
    {
        debug_Warning(Debug_ICMPv4, "ICMP packet bad checksum...");
        return NET_ERR_BROKEN;
    }
    return NET_ERR_OK;
}

/***
 * @description: (静态函数)ICMP协议向网络上发送数据包
 * @Author: Liverpool
 * @Date: 2024-07-15 17:00:18
 * @return {*} 在发送过程中出现的错误
 * @param {D_IP_ADDR} *dest 目的IP地址
 * @param {D_IP_ADDR} *ser 源IP地址
 * @param {D_Packet} *buf  需要发送的数据包的内存地址
 */
static net_err_t icmpv4_out(D_IP_ADDR *dest, D_IP_ADDR *src, D_Packet *buf)
{
    D_ICMPv4_Packet *icmp_packet = (D_ICMPv4_Packet *)packet_firstBlock_data(buf);
    packet_reset_access(buf); // 需要对数据部份重现定位
    display_icmpv4_packet(icmp_packet);
    icmp_packet->header.checksum = packet_checksum_16(buf, buf->total_size, 0, 1);
    return ipv4_out(Net_Protocol_ICMPv4, dest, src, buf); // 使用IP协议将数据包发送出去
}

/***
 * @description:(静态函数)ICMP协议在收到ping请求后需要进行回应
 * @Author: Liverpool
 * @Date: 2024-07-15 16:45:34
 * @return {*} 在发送回应请求中出现的错误
 * @param {D_IP_ADDR} *dest 目的IP地址
 * @param {D_IP_ADDR} *ser 源IP地址
 * @param {D_Packet} *buf 收到的请求的ICMP协议数据包(发送也是该数据包)
 */
static net_err_t icmpv4_echo_replay(D_IP_ADDR *dest, D_IP_ADDR *src, D_Packet *buf)
{
    D_ICMPv4_Packet *icmp_packet = (D_ICMPv4_Packet *)packet_firstBlock_data(buf);
    icmp_packet->header.type = ICMPv4_ECHO_REPLY;
    icmp_packet->header.checksum = 0;
    return icmpv4_out(dest, src, buf);
}

/***
 * @description: 对ICMPv4协议的模块进行初始化处理
 * @Author: Liverpool
 * @Date: 2024-07-13 19:47:39
 * @return {*} 在初始化过程中出现的错误
 */
net_err_t icmpv4_init(void)
{
    debug_info(Debug_ICMPv4, "ICMPv4 init...");
    debug_info(Debug_ICMPv4, "ICMPv4 init done...");
    return NET_ERR_OK;
}

/***
 * @description: 对ICMP协议的数据报做数据解析
 * @Author: Liverpool
 * @Date: 2024-07-13 20:28:11
 * @return {*} 解析过程中出现的错误
 * @param {D_IP_ADDR} *src_ip 发送的源IP地址
 * @param {D_IP_ADDR} *netif_ip 接收到的网卡的IP地址
 * @param {D_Packet} *buf 收到的数据包的内存地址
 */
net_err_t icmpv4_in(D_IP_ADDR *src_ip, D_IP_ADDR *netif_ip, D_Packet *buf)
{
    debug_info(Debug_ICMPv4, "ICMPv4 in...");
    D_IPv4_Packet *ip_packet = (D_IPv4_Packet *)packet_firstBlock_data(buf);              // 使用IP数据报解析数据区
    int ip_header_size = ipv4_header_size(ip_packet);                                     // 得到IP数据报的长度
    net_err_t err = packet_set_continuous(buf, ip_header_size + sizeof(D_ICMPv4_Header)); // 对数据包进行连续性设置
    if (err < 0)
    {
        debug_error(Debug_ICMPv4, "ICMPv4 set continuous failed, err = %d...", err);
        return err;
    }
    ip_packet = (D_IPv4_Packet *)packet_firstBlock_data(buf); // 重现设置IP数据报报头位置

    D_ICMPv4_Packet *icmp_packet = (D_ICMPv4_Packet *)(packet_firstBlock_data(buf) + ip_header_size);
    packet_seek(buf, ip_header_size); // 调整数据位置
    if ((err = is_ICMPv4_packet_ok(icmp_packet, buf->total_size, buf)) != NET_ERR_OK)
    {
        debug_Warning(Debug_ICMPv4, "ICMP packet error , err = %d...", err);
        return err;
    }
    display_icmpv4_packet(icmp_packet);
    // 根据不同的数据包中的协议类型进行分类处理
    switch (icmp_packet->header.type)
    {
    case ICMPv4_ECHO_REQUEST:
        err = packet_remove_header(buf, ip_header_size); // 移除IP数据报的报头
        if (err < 0)
        {
            debug_error(Debug_ICMPv4, "ICMPv4 remove IPv4 header failed, err =%d...", err);
            return NET_ERR_SIZE;
        }
        packet_reset_access(buf); // 重现定位数据包的数据位置

        return icmpv4_echo_replay(src_ip, netif_ip, buf);
        break;
    default:
        err = raw_in(buf); // 其他协议进入raw处理
        if (err < 0)
        {
            debug_Warning(Debug_ICMPv4, "raw in failed, err=%d...", err);
            return err;
        }
        break;
    }
    return NET_ERR_OK;
}

/***
 * @description: ICMP协议向网络发送不可达数据包
 * @Author: Liverpool
 * @Date: 2024-07-16 00:03:57
 * @return {*} 发送过程中出现的错误
 * @param {D_IP_ADDR} *dest 目的地址
 * @param {D_IP_ADDR} *src 源地址
 * @param {uint8_t} code 不可达的代码
 * @param {D_Packet} *buf 接收到的数据包的内存地址
 */
net_err_t icmpv4_out_unreach(D_IP_ADDR *dest, D_IP_ADDR *src, uint8_t code, D_Packet *buf)
{
    // 1.计算需要复制的字节数
    int copy_size = ipv4_header_size((D_IPv4_Packet *)packet_firstBlock_data(buf)) + 576;
    if (copy_size > buf->total_size)
    {
        copy_size = buf->total_size;
    }
    // 2.分配一个数据包，预留空间
    D_Packet *new_buf = packet_alloc(copy_size + sizeof(D_ICMPv4_Header) + 4);
    if (new_buf == (D_Packet *)0)
    {
        debug_Warning(Debug_ICMPv4, "alloc buf failed...");
        return NET_ERR_SIZE;
    }
    // 3.进行连续设置
    net_err_t err = packet_set_continuous(new_buf, sizeof(D_ICMPv4_Packet));
    if (err < 0)
    {
        debug_Warning(Debug_ICMPv4, "set continous failed...");
        return NET_ERR_SIZE;
    }
    // 4. 生成ICMP包头
    D_ICMPv4_Packet *icmp_packet = (D_ICMPv4_Packet *)packet_firstBlock_data(new_buf);
    icmp_packet->header.type = ICMPv4_UNREACH;
    icmp_packet->header.code = code;
    icmp_packet->header.checksum = 0;
    icmp_packet->reserve = 0;
    // 5.ICMP数据部分写入
    packet_reset_access(buf);
    packet_seek(new_buf, sizeof(D_ICMPv4_Header) + 4);
    err = packet_copy(new_buf, buf, copy_size);
    if (err < 0)
    {
        debug_error(Debug_ICMPv4, "copy ip buf failed, err = %d...", err);
        packet_free(new_buf);
        return err;
    }
    // 6.发包
    err = icmpv4_out(dest, src, new_buf);
    if (err < 0)
    {
        debug_error(Debug_ICMPv4, "send icmp unreach failed, err = %d...", err);
        packet_free(new_buf);
        return err;
    }
    return NET_ERR_OK;
}